package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.sql.SQLException;

/**
 * Handles all communication with a client, interprets protocol messages 
 * and responds to requests
 */
public class Client implements Runnable{
	Socket socket;
	PrintWriter out;
	BufferedReader in;
	Catalog catalog;
	int listenerPort;
	boolean pinged;
	
	/**
	 * Default constructor
	 * @param socket socket that is created byt accepting client connection on ServerSocket
	 * @param catalog reference to catalog
	 * @throws UnsupportedEncodingException Communication is made with incorrect encoding
	 * @throws IOException failed to get InputStream or OutputStream from socket
	 */
	public Client(Socket socket, Catalog catalog) throws UnsupportedEncodingException, IOException{
		pinged = true;
		out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"), true);
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		this.catalog = catalog;
		this.socket = socket;
		Thread thread = new Thread(this);
		thread.start();
	}

	/**
	 * Constantly listens for messages from client.
	 * Calls for removal of client on lost connection.
	 */
	@Override
	public void run() {
		try {
			String message;
			while((message = in.readLine()) != null){
				interpretMessage(message);
			}
		} catch (IOException e) {
			catalog.removeClient(this);
		}
	}
	
	/**
	 * Gets InetAddress that socket is connected to
	 * @return inetAddress that socket is connected to
	 */
	public InetAddress getAddress(){
		return socket.getInetAddress();		
	}
	
	/**
	 * Sends message to client
	 * @param message String message to be sent
	 */
	public void messageToClient(String message){
		out.println(message);
		out.flush();
	}
	
	/**
	 * Gets port number that socket is connected to
	 * @return port number
	 */
	public int getPort(){
		return socket.getPort();
	}
	
	/**
	 * Interprets message and calls the correct methods
	 * @param message message to be interpreted
	 */
	private void interpretMessage(String message){
		String command = getMessageCommand(message);
		String content = getMessageContent(message);
		
		if (command.equalsIgnoreCase("register")){
			try {
				catalog.registerFiles(content, this);
			} catch (SQLException e) {
				System.out.println("Could not store files from " + this.toString() + ": " + e);
			}
		} else if (command.equalsIgnoreCase("port") && !content.equalsIgnoreCase("")){
			listenerPort = Integer.parseInt(content);
		} else if (command.equalsIgnoreCase("request") && !content.equalsIgnoreCase("")){
			String result;
			try {
				result = catalog.searchFor(content, this);
			} catch (SQLException e) {
				result = "result:no_found";
			}
			messageToClient(result);
		} else if (command.equalsIgnoreCase("unregister")){
			catalog.clearClientFiles(this);
		} else if (command.equalsIgnoreCase("ping")){
			pinged = true;
		} else {
			System.out.println("Client@" + this.getAddress() + " sent an unknown command: " + message);
		}
	}
	
	/**
	 * Gets content information by removing protocol specific info
	 * @param message Message
	 * @return String with message content
	 */
	private String getMessageContent(String message){
		String[] stringArray = message.split(":");
		if(stringArray.length>1){
			return stringArray[1];
		} else return "";
	}
	
	/**
	 * Gets protocol specific info
	 * @param message Message
	 * @return String with protocol message
	 */
	private String getMessageCommand(String message){
		String[] stringArray = message.split(":");
		return stringArray[0];
	}
	
	/**
	 * Returns a String with inetAdress and port
	 */
	public String toString(){
		return socket.getInetAddress() + ":" + this.listenerPort;
	}

	/**
	 * Set client as not pinged
	 */
	public void unPing() {
		pinged = false;
	}
	
	/**
	 * Client ping status
	 * @return boolean on if client is pinged
	 */
	public boolean isPinged(){
		return pinged;
	}
}
